MP#03: Visualizing and Maintaining the Green Canopy of NYC

Author

Rashika Auti

Urban Roots: A Data-Driven Look

Introduction

New York City’s parks and green spaces are a vital part of the urban landscape, maintained by the Department of Parks and Recreation with over 5,000 employees and nearly 900,000 trees across more than 30,000 acres. This project explores the NYC TreeMap dataset to create clear, informative visualizations, and uses these insights to propose a new program aimed at maximizing the benefits of the city’s urban trees for all residents.

Data Acquisition

The analysis relies on two primary datasets: the NYC City Council District boundaries and the NYC Tree Points. The district boundaries are obtained as a static shapefile, while the tree locations are retrieved programmatically via the NYC OpenData API using a controlled, paginated approach. Together, these datasets provide the essential spatial framework for subsequent analyses.

Dataset#1: NYC City Council District Boundaries
To analyze tree distribution across New York City, it is essential to first obtain the geographic boundaries of the 51 City Council districts. These boundaries provide the spatial framework necessary to aggregate and interpret tree data at the district level.

View R Code
if(!dir.exists(file.path("data", "mp03"))){
    dir.create(file.path("data", "mp03"), showWarnings=FALSE, recursive=TRUE)
}

get_council_districts_wgs84 <- function() {
City_Council_Clipped_to_Shoreline <- file.path("data", "mp03", "City_Council_Clipped_to_Shoreline.zip")

if(!file.exists(City_Council_Clipped_to_Shoreline)){
    download.file("https://s-media.nyc.gov/agencies/dcp/assets/files/zip/data-tools/bytes/city-council/nycc_25c.zip", 
                  destfile=City_Council_Clipped_to_Shoreline, 
                  headers = c(`User-Agent` = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0")
    )
}

# Unzipping the file using unzip command
unzip(City_Council_Clipped_to_Shoreline, exdir = file.path("data", "mp03"))

# Reading the shp file using sf::st_read command
library(sf)

# Path to the unzipped folder containing the shapefile
shp_dir <- "data/mp03/nycc_25c"

# List all files in the folder to identify the .shp file
list.files(shp_dir)

# Read the shapefile
shp_file <- file.path(shp_dir, "nycc.shp")
council_districts <- st_read(shp_file)

# Transform the result to WGS 84
st_transform(council_districts, crs = "WGS84")

}

council_districts_wgs84 <- get_council_districts_wgs84()

Next, we display the collected data as a dataframe for easier interpretation.

View R Code
library(DT)
library(stringr)
library(dplyr)
library(sf)
library(htmltools)

format_titles <- function(df){
  colnames(df) <- str_replace_all(colnames(df), "_", " ") |> str_to_title()
  df
}

council_districts_wgs84 |>
  rename(
    Council_District = CounDist,
    Shape_Length_m = Shape_Leng,
    Shape_Area_m2   = Shape_Area
  ) |>
  mutate(
    Shape_Length = Shape_Length_m * 0.000621371,    # meters → miles
    Shape_Area   = Shape_Area_m2 * 3.861021e-7      # m² → mi²
  ) |>
  select(-Shape_Length_m, -Shape_Area_m2) |>
  format_titles() |>
  datatable(
    options = list(searching = FALSE, info = FALSE),
    colnames = c(
      "Council District",
      "Shape Length (Miles)",
      "Shape Area (Sq. Miles)",
      "Geometry"
    )
  ) |>
  formatRound(columns = c('Shape Length', 'Shape Area'), digits = 2)

The geometry column in an sf dataframe does not contain text or numeric values; instead, it stores complex spatial objects (MULTIPOLYGONs) that define the boundaries of each district. Since these geometries cannot be visually rendered within a standard dataframe, they appear as placeholders in the output. Next, we visualize the spatial data using the ggplot2 package to better understand the geographic distribution of the council districts.

View R Code
library(ggplot2)

ggplot(council_districts_wgs84) +
  geom_sf(fill = "NA", color = "black") +
  theme_minimal() +
  theme(
    plot.background = element_rect(fill = "#E6F0FA"),   # background around the entire plot
    panel.grid.major = element_line(color = "grey80", size = 0.3),  # slightly visible major grid lines
    panel.grid.minor = element_line(color = "grey90", size = 0.2),  # subtle minor grid lines
    plot.title = element_text(hjust = 0.5)
  ) +
  labs(title = "NYC Council District Boundaries")

Full-resolution geometry captures every detail of district boundaries, but plotting large spatial datasets can be slow. By using the dTolerance parameter in st_simplify(), we can simplify these boundaries, speeding up rendering while preserving the overall shapes. This makes visualizations more efficient without losing important spatial context.

View R Code
# Re-rendering the plot with reduced resolution using st_simplify() and the dTolerance parameter set to 50
library(dplyr)
library(ggplot2)
library(sf)
ggplot(council_districts_wgs84 |> 
         mutate(geometry = st_simplify(geometry, dTolerance = 50))) +
  geom_sf(fill = "NA", color = "black") +
  theme_minimal() +
  theme(
    plot.background = element_rect(fill = "#E6F0FA"),   # background around the entire plot
    panel.grid.major = element_line(color = "grey80", size = 0.3),  # slightly visible major grid lines
    panel.grid.minor = element_line(color = "grey90", size = 0.2),  # subtle minor grid lines
    plot.title = element_text(hjust = 0.5)
  ) +
  labs(title = "NYC Council District Boundaries (Reduced Resolution)", hjust = 0.5)

Dataset#2: NYC Tree Points
To assess the distribution, species, and characteristics of urban trees, the NYC Tree Points dataset provides detailed geospatial information for nearly 900,000 trees across the city. This dataset is accessed via the NYC OpenData API and forms the foundation for all tree-level analyses.

This code downloads the NYC Tree Points dataset in manageable chunks to avoid overloading the server. The get_nyc_tree_points() function fetches data in batches, controlled by limit (how many trees per request) and offset (where to start each batch). Each batch is saved locally and read into R with st_read() from the sf package, then combined using bind_rows(). The httr2 functions handle the API call responsibly, with retries and a custom User-Agent. By enabling chunk caching (#| cache: true), this process runs only once, speeding up website rendering.

View R Code
##| cache: false  # avoid caching large objects in knitr
library(httr2)  # API calls
library(sf)     # spatial data
library(dplyr)  # data manipulation

# Directory to store raw data and final RDS
nyc_tree_points_raw_data <- "data/mp03"
if(!dir.exists(nyc_tree_points_raw_data)){
  dir.create(nyc_tree_points_raw_data, recursive = TRUE, showWarnings = FALSE)
}

# RDS file path to save/load entire dataset
tree_rds_file <- file.path(nyc_tree_points_raw_data, "nyc_tree_points.rds")

# Function to download Tree Points data in batches
get_nyc_tree_points <- function(limit = 50000, nyc_tree_points_raw_data = "data/mp03") {
  
  url <- "https://data.cityofnewyork.us/resource/hn5i-inap.geojson"
  
  combined_data <- list()
  offset <- 0
  batch_num <- 1
  
  repeat {
    # Temporary file path for each batch
    interim_file <- file.path(nyc_tree_points_raw_data, paste0("tree_points_batch_", batch_num, ".geojson"))
    
    # Download batch only if file doesn't exist
    if(!file.exists(interim_file)) {
      request(url) |>
        req_url_query(`$limit` = limit, `$offset` = offset) |> 
        req_headers(`User-Agent` = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:143.0) Gecko/20100101 Firefox/143.0") |>
        req_retry(max_tries = 5) |>
        req_perform(path = interim_file)
    }
    
    # Read GeoJSON batch
    batch_data <- st_read(interim_file, quiet = TRUE)
    
    # Stop if batch is empty (end of dataset)
    if(nrow(batch_data) == 0) break
    
    combined_data[[batch_num]] <- batch_data
    
    # Update offset and batch index
    offset <- offset + nrow(batch_data)
    batch_num <- batch_num + 1
  }
  
  # Combine all batches into a single sf object
  nyc_tree_points <- bind_rows(combined_data)
  return(nyc_tree_points)
}

# Load data: either from saved RDS or download if not present
if (!file.exists(tree_rds_file)) {
  nyc_tree_points <- get_nyc_tree_points()
  
  # Save the complete dataset for future use
  saveRDS(nyc_tree_points, tree_rds_file)
} else {
  nyc_tree_points <- readRDS(tree_rds_file)
}

To get a quick glimpse of the NYC Tree Points dataset, we display a random sample of 50 observations using an interactive table below.

View R Code
library(DT)
library(stringr)
library(dplyr)
nyc_tree_points |>
  slice_sample(n=50) |>
  datatable(options=list(searching=FALSE, info=FALSE))

Data Integration and Initial Exploration

Before preparing the final report, it’s helpful to visually explore how trees are distributed across New York City. This allows us to gain an initial understanding of spatial patterns, identify areas with dense or sparse tree coverage, and uncover potential insights for further analysis.

Mapping NYC Trees

In this step, we will create a ggplot2 map that overlays all tree locations across New York City on top of the City Council district boundaries.

This visualization involves layering two spatial elements: the district boundaries (POLYGONs) and individual tree points (POINTs); using multiple geom_sf() layers within ggplot2. Each layer will take its own dataset and mapping arguments, allowing us to combine different spatial geometries in one coherent map. Since NYC has a large number of trees, we will fine-tune the plot’s appearance (e.g., transparency, point size) to ensure the visualization remains clear and readable.

View R Code
library(ggplot2)
library(sf)
library(dplyr)

# Add dummy variables for legend
districts_legend <- council_districts_wgs84 |> 
  mutate(geometry = st_simplify(geometry, dTolerance = 50),
         Layer = "Council District")

trees_legend <- nyc_tree_points |> 
  mutate(Layer = "Tree Point")

# Layered plot with legend
ggplot() +
  # Layer 1: NYC Council District Boundaries (POLYGON)
  geom_sf(data = districts_legend, 
          aes(color = Layer), 
          fill = NA,
          size = 0.5, 
          alpha = 0.7) +
  
  # Layer 2: NYC Tree Points (POINT)
  geom_sf(data = trees_legend, 
          aes(color = Layer), 
          size = 0.3, 
          alpha = 0.3) +
  
  # Color mapping for legend
  scale_color_manual(values = c("Council District" = "black", "Tree Point" = "lightgreen")) +
  
  # Theme adjustments
  theme_minimal() +
  theme(
    panel.grid.major = element_line(color = "grey80", size = 0.3),  # slightly visible major grid lines
    panel.grid.minor = element_line(color = "grey90", size = 0.2),  # subtle minor grid lines
    plot.title = element_text(size = 12, face = "bold", hjust = 0.5),
    plot.subtitle = element_text(size = 9, hjust = 0.5),
    legend.position = "bottom"
  ) +
  
  # Titles
  labs(
    title = "NYC Tree Points Across Council Districts",
    subtitle = "Trees superimposed over the City Council district boundaries",
    color = "Layer"
  )

The resulting map shows all NYC trees overlaid on City Council districts. Black borders outline the districts, while small green points represent trees. Transparency (alpha) highlights areas with more trees, making dense clusters visible without clutter. The clean layout and titles make it easy to see how tree distribution varies across districts.

District-Level Analyses of Trees (Spatial join)

Next, we want to understand how trees are distributed across different City Council districts in NYC. To do this, we need to connect each tree to the district it belongs to. Unlike simple table joins, this requires a spatial join because we are matching points (trees) to polygons (districts). Using the st_join() function from the sf package, we can assign trees to districts based on their location, which sets the stage for district-level summaries and visualizations.

When using st_contains(), the polygon must be the first argument and the points the second; and vice-versa for st_intersects().

View R Code
##| cache: false  # avoid caching in knitr
#| output: false
library(sf)
library(dplyr)

# File path to save/load the spatial join result
spatial_join_file <- file.path("data/mp03", "nyc_trees_districts_joined.rds")

if (!file.exists(spatial_join_file)) {
  
  # Perform the spatial join (points that lie within simplified polygons)
  nyc_trees_districts_joined <- st_join(
    nyc_tree_points,
    council_districts_wgs84,
    join = st_within
  )
  
  # Save the result for future use
  saveRDS(nyc_trees_districts_joined, spatial_join_file)
  
} else {
  # Load precomputed spatial join
  nyc_trees_districts_joined <- readRDS(spatial_join_file)
}

# Quick preview
# head(nyc_trees_districts_joined)

Let us explore a few sample data points generated from our spatial join to get a sense of the dataset using an interactive datatable.

View R Code
nyc_trees_districts_joined |>
  slice_sample(n=50) |>
  datatable(options=list(searching=FALSE, info=FALSE))

Next, we quantify tree distribution by district. Each tree point is assigned to its respective City Council district, and the total number of trees per district is calculated. These results are then visualized as a choropleth map, where districts with more trees appear green and those with fewer trees appear yellow. This color gradient provides an intuitive view of tree density, making it easy to identify areas with dense tree coverage and districts that may benefit from additional planting, offering a clear picture of NYC’s urban forest distribution.

View R Code
library(ggplot2)
library(dplyr)
library(sf)
library(RColorBrewer)

# Count number of trees per district (tree density)
tree_density <- nyc_trees_districts_joined |>
  st_drop_geometry() |>  
  group_by(CounDist) |>
  summarise(Tree_Count = n())

# Merge tree density with council district geometry
council_with_density <- council_districts_wgs84 |>
  left_join(tree_density, by = "CounDist") |>
  mutate(Tree_Count = ifelse(is.na(Tree_Count), 0, Tree_Count))


# Plot with light blue background and subtle gridlines
ggplot(council_with_density) +
  geom_sf(aes(fill = Tree_Count), color = "white", size = 0.3) +
  scale_fill_distiller(palette = "YlGn", direction = 1, name = "Number of Trees") +
  theme_minimal() +
  theme(
    panel.grid.major = element_line(color = "grey80", size = 0.3),  # slightly visible major grid lines
    panel.grid.minor = element_line(color = "grey90", size = 0.2),  # subtle minor grid lines
    plot.title = element_text(size = 12, face = "bold", hjust = 0.5),
    plot.subtitle = element_text(size = 9, hjust = 0.5),
    legend.position = "right"
  ) +
  labs(
    title = "Tree Distribution Across 51 NYC Council Districts",
    subtitle = "Council Districts shaded by the number of trees recorded within their boundaries"
  )

With the tree density data by district now prepared, we can turn our attention to exploring it further. This next step focuses on deriving insights and answering key questions about tree distribution, density, and patterns across NYC’s City Council districts.

Key Exploratory Questions

Which council district has the most trees?

Interestingly, one of the Staten Island districts appears to have the deepest green, suggesting it may host the most trees. As per the above map, this visual cue hints at where the maximum number of trees might be. Let’s now verify this by calculating the exact counts and identifying which council district truly has the maximum number of trees.

View R Code
# Create summarized data (before turning into datatable)

library(dplyr)
library(sf)
library(DT)
library(stringr)

# Summarize tree counts by district
max_tree_count <- nyc_trees_districts_joined |>
  st_drop_geometry() |>
  group_by(CounDist) |>
  summarise(Tree_Count = n()) |>
  ungroup() |>
  # Add Borough based on council district ranges
  mutate(Borough = case_when(
    CounDist >= 1  & CounDist <= 10 ~ "Manhattan",
    CounDist >= 11 & CounDist <= 18 ~ "Bronx",
    CounDist >= 19 & CounDist <= 32 ~ "Queens",
    CounDist >= 33 & CounDist <= 48 ~ "Brooklyn",
    CounDist >= 49 & CounDist <= 51 ~ "Staten Island",
    TRUE ~ "Other"
  )) |>
  arrange(desc(Tree_Count)) |>
  select(Borough, `Council District` = CounDist, Tree_Count)  # Move Borough to the first column

# Pull the first (top) district and its count
top_district <- max_tree_count$`Council District`[1]
top_borough  <- max_tree_count$Borough[1]
top_tree_count <- format(max_tree_count$Tree_Count[1], big.mark = ",")

# Create datatable with formatted output
max_tree_count |>
  format_titles() |>
  datatable(options = list(searching = FALSE, info = FALSE)) |>
  formatRound(c('Tree Count'), 0) |>
  formatStyle(0, target = 'row', backgroundColor = styleEqual(1:5, '#32CD32'))

🌳 Council District 51 located in Staten Island has the highest number of trees in New York City, with approximately 70,927 trees.


Which council district has the highest density of trees? The Shape_Area column from the district shape file will be helpful here.

To determine which council district has the highest tree density, we calculated the number of trees per unit area using the Shape_Area column from the district shapefile. By dividing the total number of trees in each district by its corresponding area, we could compare tree density across districts and determine which district has the most densely distributed tree population.

View R Code
library(dplyr)
library(DT)

# Conversion factor: 1 m² = 3.861e-7 square miles
m2_to_sq_miles <- 3.861e-7

district_tree_density <- nyc_trees_districts_joined |>
  st_set_geometry(NULL) |>
  group_by(CounDist) |>
  summarise(
    Tree_Count = n(),
    `Council District Area (Sq. Miles)` = first(Shape_Area) * m2_to_sq_miles
  ) |>
  ungroup() |>
  mutate(
    `Tree Density (Per Sq. Mile)` = Tree_Count / `Council District Area (Sq. Miles)`,
    Borough = case_when(
      CounDist >= 1  & CounDist <= 10 ~ "Manhattan",
      CounDist >= 11 & CounDist <= 18 ~ "Bronx",
      CounDist >= 19 & CounDist <= 32 ~ "Queens",
      CounDist >= 33 & CounDist <= 48 ~ "Brooklyn",
      CounDist >= 49 & CounDist <= 51 ~ "Staten Island",
      TRUE ~ "Other"
    ),
    `Council District` = CounDist
  ) |>
  select(Borough, `Council District`, Tree_Count, `Council District Area (Sq. Miles)`, `Tree Density (Per Sq. Mile)`) |>
  arrange(desc(`Tree Density (Per Sq. Mile)`))

# Highlight top districts
top_density_district <- district_tree_density$`Council District`[1]
highest_density <- round(district_tree_density$`Tree Density (Per Sq. Mile)`[1], 2)
highest_density_tree_count <- format(district_tree_density$Tree_Count[1], big.mark = ",")

# Display datatable
district_tree_density |>
  format_titles() |>
  datatable(options = list(searching = FALSE, info = FALSE)) |>
  formatRound(c("Tree Density (Per Sq. Mile)", "Council District Area (Sq. Miles)"), 2) |>
  formatRound("Tree Count", 0) |>
  formatStyle(0, target = 'row', backgroundColor = styleEqual(1:5, '#32CD32'))

🌳 District 7 tops the city in tree coverage, with a density of 729.18 trees per square mile of area, totaling 15,537 trees.


Which district has highest fraction of dead trees out of all trees?

This code groups all trees by council district, counts the total trees and how many are dead, and calculates the fraction/ ratio of dead trees in each district. Finally, it sorts the results to identify the district with the highest proportion of dead trees.

View R Code
library(dplyr)
library(DT)

# Compute fraction of dead trees per district
dead_tree_fraction <- nyc_trees_districts_joined |>
  st_drop_geometry() |>
  group_by(CounDist) |>
  summarise(
    Total_Trees = n(),
    Dead_Trees = sum(tpcondition == "Dead", na.rm = TRUE),
    Fraction_Dead = Dead_Trees / Total_Trees
  ) |>
  ungroup() |>
  mutate(
    Borough = case_when(
      CounDist >= 1  & CounDist <= 10 ~ "Manhattan",
      CounDist >= 11 & CounDist <= 18 ~ "Bronx",
      CounDist >= 19 & CounDist <= 32 ~ "Queens",
      CounDist >= 33 & CounDist <= 48 ~ "Brooklyn",
      CounDist >= 49 & CounDist <= 51 ~ "Staten Island",
      TRUE ~ "Other"
    ),
    `Council District` = CounDist,
    `Percentage Of Dead Trees` = round(Fraction_Dead * 100, 2)
  ) |>
  select(Borough, `Council District`, Total_Trees, Dead_Trees, Fraction_Dead, `Percentage Of Dead Trees`) |>
  arrange(desc(Fraction_Dead))

# Top district info
most_dead_trees_district <- dead_tree_fraction$`Council District`[1]
highest_fraction_dead <- dead_tree_fraction$`Percentage Of Dead Trees`[1]
most_dead_trees_count <- format(dead_tree_fraction$Dead_Trees[1], big.mark = ",")
most_dead_borough <- dead_tree_fraction$Borough[1]

# Display in datatable
dead_tree_fraction |>
  format_titles() |>
  datatable(options = list(searching = FALSE, info = FALSE)) |>
  formatRound(c('Total Trees', 'Dead Trees'), 0) |>
  formatRound('Fraction Dead', 4) |>
  formatRound('Percentage Of Dead Trees', 2) |>
  formatStyle(0, target = 'row', backgroundColor = styleEqual(1:5, '#FF0000'))

🌳 Council District 32 (Queens) has the highest fraction of dead trees, with approximately 14.22% of its trees (4,304) recorded as dead.


What is the most common tree species in Manhattan?

Let us identify the most common tree species found in Manhattan using our joined tree–district dataset. We first classify each council district into its respective borough using the case_when() function and then proceed to find the most common species across each borough.

View R Code
# Add a 'Borough' column based on council district ranges
nyc_trees_districts <- nyc_trees_districts_joined |>
  mutate(Borough = case_when(
    CounDist >= 1 & CounDist <= 10 ~ "Manhattan",
    CounDist >= 11 & CounDist <= 18 ~ "Bronx",
    CounDist >= 19 & CounDist <= 32 ~ "Queens",
    CounDist >= 33 & CounDist <= 48 ~ "Brooklyn",
    CounDist >= 49 & CounDist <= 51 ~ "Staten Island",
    TRUE ~ "Other"
  ))

# Filter for Manhattan and count tree species
manhattan_species <- nyc_trees_districts |>
  filter(Borough == "Manhattan") |>
  st_drop_geometry() |>
  group_by(`Tree Species (Genetic Name)` = genusspecies) |>
  summarise(Tree_Count = n()) |>
  arrange(desc(Tree_Count))

# Find the most common species found in Manhattan
most_common_species <- manhattan_species$`Tree Species (Genetic Name)`[1]
most_common_species_count <- format(manhattan_species$Tree_Count[1], big.mark = ",")

# View result in a datatable
manhattan_species |>
  format_titles() |>
  datatable(options = list(searching = FALSE, info = FALSE)) |>
  formatRound(c('Tree Count'), 0) |>
  formatStyle(0, target = 'row', backgroundColor = styleEqual(1:5, '#32CD32'))

🌳 The most common tree species in Manhattan is the Gleditsia triacanthos var. inermis - Thornless honeylocust, which dominates the borough’s urban canopy with approximately 17,311 trees.


What is the species of the tree closest to Baruch’s campus?

To find the tree species closest to Baruch College’s campus, we’ll create a spatial point for Baruch’s location and compute the distance between this point and all trees in our dataset.

View R Code
# Function to create a spatial point (Baruch College location)
library(sf)
library(dplyr)
library(DT)

nyc_trees_districts <- nyc_trees_districts_joined |>
  mutate(Borough = case_when(
    CounDist >= 1 & CounDist <= 10 ~ "Manhattan",
    CounDist >= 11 & CounDist <= 18 ~ "Bronx",
    CounDist >= 19 & CounDist <= 32 ~ "Queens",
    CounDist >= 33 & CounDist <= 48 ~ "Brooklyn",
    CounDist >= 49 & CounDist <= 51 ~ "Staten Island",
    TRUE ~ "Other"
  ))

new_st_point <- function(lat, lon) {
  st_sfc(st_point(c(lon, lat)), crs = "WGS84")
}

# Coordinates for Baruch College
baruch_point <- new_st_point(lat = 40.7404, lon = -73.9832)

# Calculate distances from each tree to Baruch
trees_near_baruch <- nyc_trees_districts |>
  filter(Borough == "Manhattan") |>
  mutate(distance = round(as.numeric(st_distance(geometry, baruch_point)), 2)) |>
  arrange(distance)

# Find the nearest species
nearest_species <- trees_near_baruch$genusspecies[1]
nearest_distance <- trees_near_baruch$distance[1]

# Prepare datatable with selected and renamed columns
trees_near_baruch |>
  slice_min(order_by = distance, n = 10) |>
  st_set_geometry(NULL) |>   # drop geometry for datatable
  select(
    `Tree Species (Genetic Name)` = genusspecies,
    `Council District` = CounDist,
    Borough,
    `Distance (in meters)` = distance,
    `Tree Condition` = tpcondition
  ) |>
  datatable(options = list(searching = FALSE, info = FALSE, pageLength = 5)) |>
  formatStyle(0, target = 'row', backgroundColor = styleEqual(1, 'lightblue'))

🌳 The tree closest to Baruch College is a Liquidambar styraciflua - sweetgum, located just about 5.86 meters from campus.

Government Project Design

Green Corridors of District 3: Renewal & Resilience Strategy

This proposal outlines the Green Corridors of District 3: Renewal & Resilience Strategy, a targeted plan to strengthen urban canopy health, enhance neighborhood livability, and prioritize data-driven tree stewardship across the district.

Proposal to NYC Parks Department

District 3 is home to some of Manhattan’s most dynamic neighborhoods, yet its tree canopy has not kept pace with the growing needs of its residents, aging infrastructure, and intensifying climate pressures. The Tree Renewal & Resilience Program seeks dedicated investment to strengthen the district’s environmental resilience, improve pedestrian safety, and expand equitable access to green spaces. This initiative focuses on restoring damaged tree stock, planting new trees in low-coverage zones, and ensuring long-term maintenance of vulnerable species.

Project Overview

This program proposes a district-wide improvement effort centered on three major components:

  • Replacing hazardous or dead trees that threaten sidewalks, buildings, and public safety.

  • Planting new, climate-resilient trees in blocks with the lowest canopy cover to reduce heat exposure and improve air quality.

  • Launching a community stewardship campaign to support maintenance and long-term survival.

This project directly supports the City’s broader goals of climate adaptation, public health, and environmental equity.

Project Scope (Quantitative Goals)

District 3 will undertake the following measurable actions:

  • 3,364 tree removals of dead or high-risk trees (based on tpcondition and riskrating).

  • 2,835 stump eliminations, converting unused stumps into viable planting locations.

  • 541 new tree plantings using species selected for resilience, canopy growth, and maintenance feasibility.

  • Coverage improvement goal: Increase tree density in low-coverage subregions by 15% (equivalent to 1,800 new plantations) over the next two planting seasons.

Why District 3?

District 3 exhibits urgent environmental needs compared with peer districts.

Key comparative indicators:

  • District 3 has one of the lowest tree densities among nearby districts such as District 2, District 4, and District 5.

  • It has a higher proportion of dead or poor-condition trees, indicating overdue maintenance.

  • Several blocks show above-average stump counts, representing missed planting opportunities.

  • The district has fewer large-canopy species, limiting shade and heat-mitigation potential relative to comparable districts.

These comparisons clearly demonstrate that District 3 not only requires investment but also has the highest potential for measurable improvement with targeted funding.

Zoomed-In Tree Map of District 3

Note

Extra-credit#1

This visualization highlights the exact areas where replacement, removal, or planting efforts will be prioritized.

Supporting Visualizations

District 3 shows a moderate number of trees in good condition compared with neighboring districts but still has a higher proportion of poor and dead trees relative to total density. This underscores the need for targeted maintenance and strategic planting to improve overall tree health and canopy coverage in the district.

This indicates that while District 3 has moderate biodiversity, there is still significant potential to increase species diversity through targeted plantings, enhancing ecological resilience and canopy coverage.

View R Code
library(ggplot2)
library(dplyr)
library(sf)

# Count unique species per district (2-5)
species_summary <- nyc_trees_districts_joined |>
  filter(CounDist %in% 2:5) |>
  st_drop_geometry() |>
  group_by(CounDist) |>
  summarise(
    unique_species = n_distinct(genusspecies),
    .groups = "drop"
  ) |>
  mutate(
    highlight = ifelse(CounDist == 3, "District 3", "Other Districts")
  )

# Horizontal bar chart with labels
ggplot(species_summary, aes(x = reorder(factor(CounDist), unique_species), 
                            y = unique_species, fill = highlight)) +
  geom_col(width = 0.6) +
  geom_text(aes(label = unique_species), 
            hjust = -0.1,  # slightly outside the bar
            size = 4, 
            color = "black") +
  coord_flip() +  # horizontal bars
  scale_fill_manual(values = c("District 3" = "#1b9e77", "Other Districts" = "#a8a8a8")) +
  labs(
    title = "Biodiversity Snapshot: Unique Tree Species Across Districts 2–5",
    x = "District",
    y = "Number of Unique Species",
    fill = ""
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 12, face = "bold", hjust = 0.5),
    axis.title.x = element_text(size = 12),
    axis.title.y = element_text(size = 12),
    axis.text = element_text(size = 11),
    legend.position = "none"
  ) +
  expand_limits(y = max(species_summary$unique_species) * 1.1)  # add extra space for labels

These graphics demonstrates the clear quantitative need for immediate action.

District Comparison Visualizations

District 3 in Manhattan has one of the lowest tree densities among local council districts, which means that allocating funds to tree programs here offers the potential for highly visible, high-impact improvements in urban greenery. This comparison underscores the urgent need for prioritizing tree investments in District 3 to maximize public and environmental benefits.

Districts 2 and 3 exhibit similar spatial patterns in tree conditions. While the majority of trees are in good condition, both districts contain localized clusters of poor-condition trees, indicating areas that may benefit from targeted maintenance or intervention.

Conclusion & Call to Action

Investing in the Urban Tree Renewal & Resilience Program in District 3 is a cost-effective, community-centered step toward a greener, healthier, and safer Manhattan. With strategic funding, we can transform neglected streetscapes, reduce heat vulnerability, improve air quality, and set a model for district-level environmental planning.

We respectfully request that NYC Parks allocate additional budgetary support for this program in the upcoming cycle, enabling District 3 to become a leading example of sustainable, community-driven urban forestry.